/* ------------------------------------------------------------------------- */
/*   saa7113h.c I2C driver for Philips SAA7113H video decoder chips	     */
/* ------------------------------------------------------------------------- */
/*   Copyright (C) 2000 Raphael Dorado <rdorado@pacbell.net>	    
 *   Added SAA7113H init register and modified capability detection.
 *   Copyright (C) 2000 Ferenc Bakonyi <fero@drama.obuda.kando.hu>
 *   saa7111_command() stolen from saa7111.c by Dave Perks <dperks@ibm.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *   
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *   
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.		     */
/* ------------------------------------------------------------------------- */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>

#include "rivatv-kcompat.h"
#include "saa7113h.h"

#define SAA7113H_VERSION_ID_REG	 0x00
#define SAA7113H_I2C_VERSION_ID	 0x10

/* Debug option */
static int debug = 0;

#define SAA7113H_PREFIX "SAA7113H: "
#define SAA7113H_PRINTK(format, args...)			\
	printk (SAA7113H_PREFIX format, ## args)
#define SAA7113H_DEBUG_PRINTK(format, args...) if (debug >= 1)	\
	printk (SAA7113H_PREFIX format, ## args)
#define SAA7113H_DEBUG_PRINTK2(format, args...) if (debug >= 2) \
	printk (SAA7113H_PREFIX format, ## args)
#undef	PRINTK
#define PRINTK(format, args...)					\
	printk (format, ## args)
#undef	DPRINTK
#define DPRINTK(format, args...) if (debug >= 1)		\
	printk (format, ## args)
#undef	DPRINTK2
#define DPRINTK2(format, args...) if (debug >= 2)		\
	printk (format, ## args)

#define SAA7113H_REG_ADDR(client, ptr) \
	((ulong) ptr - (ulong) &((saa7113h_t *) i2c_get_clientdata (client))->reg.field)

#define SAA7113H_BYTEVAL(a) (*((__u8 *)&(a)))

/*----------------------------------------------------------------------
 *	SAA7113H I2C 'driver' driver
 *----------------------------------------------------------------------*/

static int saa7113h_attach  (struct i2c_adapter *);
static int saa7113h_detach  (struct i2c_client *);
static int saa7113h_command (struct i2c_client *, unsigned int, void *);

/* Possible SAA7113H addresses: 0x24, 0x25 */
static __u16 normal_i2c[] = {0x24, 0x25, I2C_CLIENT_END};
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
static __u16 normal_i2c_range[] = {I2C_CLIENT_END};
#endif

/* Magic definition of all other I2C variables and things */
I2C_CLIENT_INSMOD;

/* SAA7113H instance */
typedef struct {
	union {
		saa7113h_reg_map_t field;
		u8		   byte[SAA7113H_NR_REGISTER];
	} reg;
	__u16 bright;
	__u16 contrast;
	__u16 sat;
	__u16 hue;
} saa7113h_t;

/* Initial mode (from datasheet). Should work for PAL/NTSC-M */
#if 0
static __u8 init_regs[SAA7113H_NR_REGISTER] =
{
  /* Register 0x00: chip version (read only)			       */
  /* Default: 0x11: (not used since read-only register)		       */
  0x00,

  /* Register 0x01: increment delay				       */
  /* Default: 0x08: IDEL=0b1000					       */
  0x08,

  /* Register 0x02: analog input control 1			       */
  /* Default: 0xC0: FUSE1, FUSE0				       */
  0xC0,

  /* Register 0x03: analog input control 2			       */
  /* Default: 0x33: VBSL, WPOFF, GAI28, GAI18			       */
  0x33,

  /* Register 0x04: analog input control 3			       */
  /* Default: 0x00						       */
  0x00,

  /* Register 0x05: analog input control 4			       */
  /* Default: 0x00						       */
  0x00,

  /* Register 0x06: horizontal sync start			       */
  /* Default: 0xE9: HSB=0b11101001				       */
  0xE9,

  /* Register 0x07: horizontal sync stop			       */
  /* Default: 0x0D: HSS=0b00001101				       */
  0x0D,

  /* Register 0x08: sync control				       */
  /* Default: 0x98: AUFD, HTC1, HTC0				       */
  /* FUCK: originla default: 0x98, */
  0x90,

  /* Register 0x09: luminance control				       */
  /* Default: 0x01: APER0					       */
  0x01,

  /* Register 0x0A: luminance brightness			       */
  /* Default: 0x80: BRIG=0b10000000				       */
  0x80,

  /* Register 0x0B: luminance contrast				       */
  /* Default: 0x47: CONT=0b01000111				       */
  0x47,

  /* Register 0x0C: chrominance saturation			       */
  /* Default: 0x40: SATN=0b01000000				       */
  0x40,

  /* Register 0x0D: chrominance hue control			       */
  /* Default: 0x00: HUEC=0x00000000				       */
  0x00,

  /* Register 0x0E: chrominance control				       */
  /* Default: 0x01: CHBW0					       */
  /* 0x01, changed NTSC-M -> NTSC-N */
  0x11,

  /* Register 0x0F: chrominance gain control			       */
  /* Default: 0x2a: CGAIN=0b0101010				       */
  0x2a, /* FUCK read: 0x0f */

  /* Register 0x10: format/delay control			       */
  /* Default: 0x00: YDEL=0b000					       */
  0x00,

  /* Register 0x11: output control 1*/
  /* Default: 0x0C: OEYC					       */
  /* 0x0C, original default value */
  0x0F,

  /* Register 0x12: output control 2				       */
  /* Default: 0x01: RTSE0=0b0001				       */
  /* 0x01, original default value */
  0x11,

  /* Register 0x13: output control 3				       */
  /* Default: 0x00: AOSL=0b00					       */
  0x00,

  /* Register 0x14: reserved					       */
  0x00,

  /* Register 0x15: VGATE start					       */
  /* Default: 0x00: VSTAlo=0b00000000				       */
  0x00,

  /* Register 0x16: VGATE stop					       */
  /* Default: 0x00: VSTOlo=0b00000000				       */
  0x00,

  /* Register 0x17: MSBs for VGATE control			       */
  /* Default: 0x00: VSTO8=0b0 VSTA8=0b0				       */
  0x00,

  /* Register 0x18: reserved					       */
  0x00,

  /* Register 0x19: reserved					       */
  0x00,

  /* Register 0x1A: reserved					       */
  0x00,

  /* Register 0x1B: reserved					       */
  0x00,

  /* Register 0x1C: reserved					       */
  0x00,

  /* Register 0x1D: reserved					       */
  0x00,

  /* Register 0x1E: reserved					       */
  0x00,

  /* Register 0x1F: decoder status byte				       */
  /* Default: 0x00: (read-only)					       */
  0x00,

  /* Register 0x20: reserved					       */
  0x00,

  /* Register 0x21: reserved					       */
  0x00,

  /* Register 0x22: reserved					       */
  0x00,

  /* Register 0x23: reserved					       */
  0x00,

  /* Register 0x24: reserved					       */
  0x00,

  /* Register 0x25: reserved					       */
  0x00,

  /* Register 0x26: reserved					       */
  0x00,

  /* Register 0x27: reserved					       */
  0x00,

  /* Register 0x28: reserved					       */
  0x00,

  /* Register 0x29: reserved					       */
  0x00,

  /* Register 0x2A: reserved					       */
  0x00,

  /* Register 0x2B: reserved					       */
  0x00,

  /* Register 0x2C: reserved					       */
  0x00,

  /* Register 0x2D: reserved					       */
  0x00,

  /* Register 0x2E: reserved					       */
  0x00,

  /* Register 0x2F: reserved					       */
  0x00,

  /* Register 0x30: reserved					       */
  0x00,

  /* Register 0x31: reserved					       */
  0x00,

  /* Register 0x32: reserved					       */
  0x00,

  /* Register 0x33: reserved					       */
  0x00,

  /* Register 0x34: reserved					       */
  0x00,

  /* Register 0x35: reserved					       */
  0x00,

  /* Register 0x36: reserved					       */
  0x00,

  /* Register 0x37: reserved					       */
  0x00,

  /* Register 0x38: reserved					       */
  0x00,

  /* Register 0x39: reserved					       */
  0x00,

  /* Register 0x3A: reserved					       */
  0x00,

  /* Register 0x3B: reserved					       */
  0x00,

  /* Register 0x3C: reserved					       */
  0x00,

  /* Register 0x3D: reserved					       */
  0x00,

  /* Register 0x3E: reserved					       */
  0x00,

  /* Register 0x3F: reserved					       */
  0x00,

  /* Register 0x40: slicer control 1				       */
  /* Default: 0x02: FCE ???					       */
  0x02,

  /* Register 0x41: line control register 2			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x42: line control register 3			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x43: line control register 4			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x44: line control register 5			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x45: line control register 6			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x46: line control register 7			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x47: line control register 8			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x48: line control register 9			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x49: line control register 10			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4A: line control register 11			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4B: line control register 12			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4C: line control register 13			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4D: line control register 14			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4E: line control register 15			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x4F: line control register 16			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x50: line control register 17			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x51: line control register 18			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x52: line control register 19			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x53: line control register 20			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x54: line control register 21			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x55: line control register 22			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x56: line control register 23			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x57: line control register 24			       */
  /* Default: 0xff:   TO BE ADJUSTED				       */
  0xff,

  /* Register 0x58: programmable framing code			       */
  /* Default: 0x00: FC=0b00000000				       */
  0x00,

  /* Register 0x59: horizontal offset for slicer		       */
  /* Default: 0x54: HOFFlo=0b01010100 (see reg 0x5B)		       */
  0x54,

  /* Register 0x5A: vertical offset for slicer			       */
  /* Default: 0x07: VOFFlo=0b00000111 (see reg 0x5B)		       */
  0x07,

  /* Register 0x5B: held offset and MSBs for horiz. and vert. offset   */
  /* Default: 0x83: FOFF, HOFFhi= 0b011				       */
  0x83,

  /* Register 0x5C: reserved					       */
  0x00,

  /* Register 0x5D: reserved					       */
  0x00,

  /* Register 0x5E: sliced data identification code		       */
  /* Default: 0x00: SDID=0b000000				       */
  0x00,

  /* Register 0x5F: reserved					       */
  0x00,

  /* Register 0x60: slicer status byte 1			       */
  /* Default: 0x00: (unused: read-only)				       */
  0x00,

  /* Register 0x61: slicer status byte 2			       */
  /* Default: 0x00: (unused: read-only)				       */
  0x00, /* FUCK read: 0x0e */

  /* Register 0x62: slicer status byte 3			       */
  /* Default: 0x00: (unused: read-only)				       */
  0x00, /* FUCK read: 0x0f */
};
#else
static __u8 init_regs[SAA7113H_NR_REGISTER] = {
	0x00, 0x08, 0xc0, 0x23, 0x00, 0x00, 0xe9, 0x0d,
	0xB8, 0x00, 0x80, 0x47, 0x40, 0x00, 0x01, 0x67,

	0x00, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

	0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0x00, 0x54, 0x07, 0x83, 0x00, 0x00, 0x00, 0x00,

	0x00, 0x00, 0x00
};
#endif

/* For registering the I2C 'driver' driver */
static struct i2c_driver i2c_driver_saa7113h = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16)
	.name		= "SAA7113H",
	.flags		= I2C_DF_NOTIFY,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 54) && !defined (I2C_PEC)
	.inc_use	= i2c_client_inc_use,
	.dec_use	= i2c_client_dec_use,
#else
	.owner		= THIS_MODULE,
#endif
#else
	.driver         = { .name = "SAA7113H", },
#endif
	.id		= I2C_DRIVERID_SAA7113,
	.attach_adapter = saa7113h_attach,
	.detach_client	= saa7113h_detach,
	.command	= saa7113h_command,
};

/* Template for new clients */
static struct i2c_client saa7113h_client = {
	I2C_DEVNAME ("Philips SAA7113H"),
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 12)
	.id      = -1,
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16)
	.flags   = I2C_CLIENT_ALLOW_USE,
#endif
	.addr    = 0,
	.adapter = NULL,
	.driver  = &i2c_driver_saa7113h,
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 12)
/* Unique ID allocation */
static int saa7113h_id = 0;
#endif

/* Read from a register */
static int saa7113h_read (struct i2c_client *client, void *reg)
{
	int val = i2c_smbus_read_byte_data (client, SAA7113H_REG_ADDR (client, reg));

	if (val < 0) {
		SAA7113H_PRINTK ("failed reading register 0x%02lx\n",
				 SAA7113H_REG_ADDR (client, reg));
	} else {
		(*((u8*)(reg))) = val;
	}
	return val;
}

/* Write to a register */
static int saa7113h_write (struct i2c_client *client, void *reg)
{
	int val = i2c_smbus_write_byte_data (client, SAA7113H_REG_ADDR (client, reg),
					     *(u8*)reg);

	if (val < 0) {
		SAA7113H_PRINTK ("failed writing register 0x%02lx\n",
				 SAA7113H_REG_ADDR (client, reg));
	}
	return saa7113h_read (client, reg);
}

/* Read all registers */
static int saa7113h_read_block (struct i2c_client *client)
{
	saa7113h_t *saa7113h = (saa7113h_t *) i2c_get_clientdata (client);
	int i;

	for (i = 0 ; i < SAA7113H_NR_REGISTER; i++) {
		int error;
		if ((error = saa7113h_read (client, &saa7113h->reg.byte[i])) < 0)
			return error;
	}
	return 0;
}

/* Write to a register block */
static int saa7113h_write_block (struct i2c_client *client)
{
	saa7113h_t *saa7113h = (saa7113h_t *) i2c_get_clientdata (client);
	int i;

	for (i = 0 ; i < SAA7113H_NR_REGISTER; i++) {
		int error;
		if ((error = saa7113h_write (client, &saa7113h->reg.byte[i])) < 0)
			return error;
	}
	return saa7113h_read_block (client);
}

/* Dump all registers */
static void saa7113h_dump_regs (struct i2c_client *client)
{
	saa7113h_t * saa7113h = (saa7113h_t *) i2c_get_clientdata (client);
	__u8 * bmap = saa7113h->reg.byte;
	__u32 l, c;

	if (debug >= 2) {
		/* Refresh client registers */
		saa7113h_read_block (client);

		for (l = 0; l < SAA7113H_NR_REGISTER; l += 16) {
			SAA7113H_DEBUG_PRINTK2 ("%02x |", l);
			for (c = 0; c < 16 && (l + c) < SAA7113H_NR_REGISTER; c++) {
				DPRINTK2 (" %02x", bmap[l + c]);
			}
			DPRINTK2 ("\n");
		}
	}
}


/* I2C driver functions */

/* Called when a 'SAA7113H like' device found */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
static int saa7113h_detect (struct i2c_adapter *adap, int addr, unsigned short flags, int kind)
#else
static int saa7113h_detect (struct i2c_adapter *adap, int addr, int kind)
#endif
{
	struct i2c_client * client;
	saa7113h_t *	    saa7113h;
	int err = 0;
	int version;

	if (!i2c_check_functionality (adap, I2C_FUNC_SMBUS_READ_BYTE_DATA | 
				      I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
		goto err_out;

	/* Allocate client structure */
	client = kmalloc (sizeof (*client), GFP_KERNEL);

	/* check client structure allocation error */
	if (client == NULL) {
		err = -ENOMEM;
		goto err_out;
	}
	
	/* Zero client structure fields */
	memcpy (client, &saa7113h_client, sizeof (*client));

	/* Set client's adapter */
	client->adapter = adap;

	/* Set client's address */
	client->addr = addr;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 12)
	/* Set SAA7113H client ID, update for next client */
	client->id = saa7113h_id++;
#endif

	if (kind < 0) {
		/* Get chip version */
		version = i2c_smbus_read_byte_data (client, SAA7113H_VERSION_ID_REG);

		/* If we could not get version, bail out */
		if (version == -1)
			goto err_out_kfree_client;

		/* If not a SAA7113H, bail out */
		if ((version & 0xf0) != SAA7113H_I2C_VERSION_ID) {
			SAA7113H_PRINTK ("unknown SAA711x chip found, chip version: %#x\n", version);
			goto err_out_kfree_client;
		}

		/* Chip IS a SAA7113H, log into syslog */
		SAA7113H_PRINTK ("video decoder chip found, chip version: %#x\n", version);
	}
	else {
		SAA7113H_PRINTK ("detection skipped\n");
	}
	
	saa7113h = kmalloc (sizeof (saa7113h_t), GFP_KERNEL);

	if (saa7113h == NULL) {
		SAA7113H_PRINTK ("failed allocating client data\n");
		err = -ENOMEM;
		goto err_out_kfree_client;
	}

	/* store client data pointer into client */
	i2c_set_clientdata (client, saa7113h);

	/* Initialize client registers and write them into chip */
	saa7113h->reg.field = *((saa7113h_reg_map_t *) init_regs);

	if (saa7113h_write_block (client)) {
		SAA7113H_PRINTK ("failed to write initial register values\n");
		goto err_out_kfree_decoder;
	}

	if ((err = i2c_attach_client (client))) {
		SAA7113H_PRINTK ("failed attaching client\n");
		goto err_out_kfree_decoder;
	}

	saa7113h_dump_regs (client);
	return 0;
	
 err_out_kfree_decoder:
	kfree (saa7113h);
 err_out_kfree_client:
	kfree (client);
 err_out:
	return err;
}

/* Called when a new I2C bus found */
static int saa7113h_attach (struct i2c_adapter *adap)
{
	return i2c_probe (adap, &addr_data, saa7113h_detect);
}

/* Called on exit */
static int saa7113h_detach (struct i2c_client *client)
{
	int err;

	/* Try to detach client, bail on error */
	if ((err = i2c_detach_client (client)))
		return err;

	/* Free client data */
	kfree (i2c_get_clientdata (client));

	/* Free client */
	kfree (client);

	return 0;
}

typedef struct {
	/* colour standard identifier */
	int standard;
	/* textual colour standard description */
	char *name;
} saa7113h_standard_t;

static saa7113h_standard_t saa7113h_standard[2][8] = {
	/* 50 Hz standards */
	{ { SAA7113H_CSTD_PALB,   "PAL-B"           },
	  { SAA7113H_CSTD_NTSC50, "NTSC-4.43(50Hz)" },
	  { SAA7113H_CSTD_PALN,   "PAL-N"           },
	  { SAA7113H_CSTD_NTSCN,  "NTSC-N"          },
	  { -1,                   "<Invalid 50Hz>"  },
	  { SAA7113H_CSTD_SECAM,  "SECAM"           },
	  { -1,                   "<Invalid 50Hz>"  },
	  { -1,                   "<Invalid 50Hz>"  } },

	/* 60 Hz standards */
	{ { SAA7113H_CSTD_NTSCM,  "NTSC-M"          },
	  { SAA7113H_CSTD_PAL60,  "PAL-4.43(60Hz)"  },
	  { SAA7113H_CSTD_NTSC60, "NTSC-4.43(60Hz)" },
	  { SAA7113H_CSTD_PALM,   "PAL-M"           },
	  { -1,                   "<Invalid 60Hz>"  },
	  { -1,                   "<Invalid 60Hz>"  },
	  { -1,                   "<Invalid 60Hz>"  },
	  { -1,                   "<Invalid 60Hz>"  } }
};

/* Autodetection of colour standard */
static void saa7113h_detection (struct i2c_client *client)
{
	saa7113h_t *    saa7113h = (saa7113h_t *) i2c_get_clientdata (client);
	saa7113h_reg_map_t * map = &saa7113h->reg.field;
	int n, idx, old, detected;

	/* detect 50/60Hz */
	map->sync_control.AUFD = SAA7113H_AUFD_AUTO;
	saa7113h_write (client, &map->sync_control);

	/* stay here a little eternity */
	set_current_state (TASK_UNINTERRUPTIBLE);
	schedule_timeout (HZ/10);
	set_current_state (TASK_RUNNING);

	saa7113h_read (client, &map->decoder_status);

	/* set explicitely the detected frequency */
	idx = map->decoder_status.FIDT ? 1 : 0;
	map->sync_control.AUFD = SAA7113H_AUFD_FIXED;
	map->sync_control.FSEL = map->decoder_status.FIDT;
	saa7113h_write (client, &map->sync_control);

	/* save old colour standard */
	saa7113h_read (client, &map->chrominance_control);
	old = map->chrominance_control.CSTD;

	/* set old status byte */
	map->output_control_3.OLDSB = 1;
	saa7113h_write (client, &map->output_control_3);
  
	for (detected = n = 0; n < 8; n++) {
		if (saa7113h_standard[idx][n].standard != -1) {
			/* set colour standard */
			map->chrominance_control.CSTD = saa7113h_standard[idx][n].standard;
			saa7113h_write (client, &map->chrominance_control);

			/* stay here a little eternity */
			set_current_state (TASK_UNINTERRUPTIBLE);
			schedule_timeout (HZ/10);
			set_current_state (TASK_RUNNING);

			/* check status byte */
			saa7113h_read (client, &map->decoder_status);
			if (map->decoder_status.RDCAP) {
				DPRINTK (" %s", saa7113h_standard[idx][n].name);
				detected++;
				break;
			}
		}
	}
  
	/* restore old colour standard */
	if (!detected) {
		map->chrominance_control.CSTD = old;
		saa7113h_write (client, &map->chrominance_control);
	}

	/* set default status byte */
	map->output_control_3.OLDSB = 0;
	saa7113h_write (client, &map->output_control_3);
}

/* Interface to the world */
static int saa7113h_command (struct i2c_client *client, unsigned int cmd, void *arg)
{
	saa7113h_t *	     saa7113h = (saa7113h_t *) i2c_get_clientdata (client);
	saa7113h_reg_map_t * map      = &saa7113h->reg.field;

	switch (cmd) {

#ifdef READ_REGISTERS
	case READ_REGISTERS:
		/* Read to client register map */
		saa7113h_read_block (client);

		/* Copy client map to passed arg */
		*((saa7113h_reg_map_t *) arg) = *map;

		SAA7113H_DEBUG_PRINTK ("READ_REGISTERS\n");

		saa7113h_dump_regs(client);

		return SAA7113H_NR_REGISTER;

	case WRITE_REGISTERS:
		/* Write passed map to client map */
		*map = *((saa7113h_reg_map_t *) arg);

		/* Write client map */
		saa7113h_write_block (client);

		SAA7113H_DEBUG_PRINTK ("WRITE_REGISTERS\n");

		saa7113h_dump_regs (client);

		return SAA7113H_NR_REGISTER;

	case GET_NR_OF_REGISTERS:
		SAA7113H_DEBUG_PRINTK ("GET_NR_OF_REGISTERS = %d\n",
				       SAA7113H_NR_REGISTER);
		return SAA7113H_NR_REGISTER;
#endif /* READ_REGISTERS */

#ifdef DECODER_DUMP
	case DECODER_DUMP:

		SAA7113H_DEBUG_PRINTK ("DECODER_DUMP\n");
		saa7113h_dump_regs (client);
		break;
#endif /* DECODER_DUMP */

#ifdef WRITE_REGISTER
	case WRITE_REGISTER: {
		u16 *sarg = arg;

		SAA7113H_DEBUG_PRINTK ("WRITE_REGISTER\n");
		saa7113h->reg.byte[*sarg >> 8] = *sarg & 0xff;
		saa7113h_write (client, &saa7113h->reg.byte[*sarg >> 8]);
		break;
	}
#endif /* WRITE_REGISTER */

	case DECODER_GET_CAPABILITIES: {
		struct video_decoder_capability *cap = arg;

		SAA7113H_DEBUG_PRINTK ("DECODER_GET_CAPABILITIES\n");
		cap->flags
			= VIDEO_DECODER_PAL
			| VIDEO_DECODER_SECAM
			| VIDEO_DECODER_NTSC
			| VIDEO_DECODER_AUTO
			| VIDEO_DECODER_CCIR;
		cap->inputs = 4;
		cap->outputs = 2;
		break;
	}


	case DECODER_GET_STATUS: {
		int *iarg = arg;
		int res;

		saa7113h_read (client, &map->decoder_status);
		saa7113h_read (client, &map->chrominance_control);
		saa7113h_read (client, &map->sync_control);
		SAA7113H_DEBUG_PRINTK ("DECODER_GET_STATUS = 0x%02x ->",
				       SAA7113H_BYTEVAL (map->decoder_status));
		res = 0;

		/* Get horizontal/vertical locking state */
		if (map->decoder_status.HLVLN == 0) {
			DPRINTK (" GOOD");
			res = DECODER_STATUS_GOOD;
		}
		else {
			DPRINTK (" BAD");
		}

		/* Get or detect decoder norm */
		if (map->sync_control.AUFD == SAA7113H_AUFD_AUTO) {
			DPRINTK (" AUTODETECT");
			if (map->decoder_status.FIDT) { /* 60/50 hz */
				res |= DECODER_STATUS_NTSC;
			} else {
				res |= DECODER_STATUS_PAL;
			}
		}
		else {
			if (map->sync_control.FSEL == SAA7113H_FSEL_50HZ) {
				res |= DECODER_STATUS_PAL;
			}
			else {
				res |= DECODER_STATUS_NTSC;
			}
		}

		if (res & DECODER_STATUS_PAL) {
			switch (map->chrominance_control.CSTD) {
			case SAA7113H_CSTD_PALB:
				DPRINTK (" PAL-B");
				break;

			case SAA7113H_CSTD_NTSC50:
				DPRINTK (" NTSC-4.43(50Hz)");
				break;

			case SAA7113H_CSTD_PALN:
				DPRINTK (" PAL-N");
				break;

			case SAA7113H_CSTD_NTSCN:
				DPRINTK (" NTSC-N");
				break;

			case SAA7113H_CSTD_SECAM:
				res = (res & ~DECODER_STATUS_PAL) | DECODER_STATUS_SECAM;
				DPRINTK (" SECAM");
				break;
			}
		}
		else {
			switch (map->chrominance_control.CSTD) {
			case SAA7113H_CSTD_NTSCJ:
				if (map->luminance_brightness.BRIG == SAA7113H_BRIG_NTSCJ &&
				    map->luminance_contrast.CONT   == SAA7113H_CONT_NTSCJ) {
					DPRINTK (" NTSC-Japan");
				}
				else {
					DPRINTK (" NTSC-M");
				}
				break;

			case SAA7113H_CSTD_PAL60:
				DPRINTK (" PAL-4.43(60Hz)");
				break;

			case SAA7113H_CSTD_NTSC60:
				DPRINTK (" NTSC-4.43(60Hz)");
				break;

			case SAA7113H_CSTD_PALM:
				DPRINTK (" PAL-M");
				break;

			case SAA7113H_CSTD_SECAM:
				DPRINTK (" <Invalid SECAM(60Hz)>");
				break;
			}
		}

		if (map->decoder_status.WIPA) {
			DPRINTK (" WHITE_PEAK");
		}

		if (map->decoder_status.COPRO) { /* copy protected source ? */
			DPRINTK (" PROTECTED");
		}

		DPRINTK ("\n");
		*iarg = res;
		break;
	}

	case DECODER_SET_NORM: {
		int *iarg = arg;
		int hoff, voff, foff, fiset;

		SAA7113H_DEBUG_PRINTK ("DECODER_SET_NORM: ");

		switch (*iarg) {

		case VIDEO_MODE_NTSC:
			DPRINTK ("NTSC");
			map->sync_control.AUFD = SAA7113H_AUFD_FIXED;
			map->sync_control.FSEL = SAA7113H_FSEL_60HZ;
			map->chrominance_control.CSTD = SAA7113H_CSTD_NTSCM;
			saa7113h_write (client, &map->sync_control);
			saa7113h_write (client, &map->chrominance_control);
			break;

		case VIDEO_MODE_PAL:
			DPRINTK ("PAL");
			map->sync_control.AUFD = SAA7113H_AUFD_FIXED;
			map->sync_control.FSEL = SAA7113H_FSEL_50HZ;
			map->chrominance_control.CSTD = SAA7113H_CSTD_PALB;
			saa7113h_write (client, &map->sync_control);
			saa7113h_write (client, &map->chrominance_control);
			break;

		case VIDEO_MODE_AUTO:
			DPRINTK ("AUTO");
			saa7113h_detection (client);
			break;

		case VIDEO_MODE_SECAM:
			DPRINTK ("SECAM");
			map->sync_control.AUFD = SAA7113H_AUFD_FIXED;
			map->sync_control.FSEL = SAA7113H_FSEL_50HZ;
			map->chrominance_control.CSTD = SAA7113H_CSTD_SECAM;
			saa7113h_write (client, &map->sync_control);
			saa7113h_write (client, &map->chrominance_control);
			break;

		default:
			DPRINTK (" <invalid value %d>\n", *iarg);
			return -EINVAL;
		}

		DPRINTK ("\n");

		/* setup slicer depending on 50/60 hz */
		if (map->sync_control.FSEL == SAA7113H_FSEL_50HZ) {
			voff = 0x007; hoff = 0x354; foff = 1; fiset = 0;
		}
		else {
			voff = 0x00a; hoff = 0x354; foff = 1; fiset = 1;
		}
		map->slicer_vertical_offset.VOFF = voff & 0xff;
		map->slicer_extra_offsets.VOFF_MSB = voff >> 8;
		map->slicer_horizontal_offset.HOFF = hoff & 0xff;
		map->slicer_extra_offsets.HOFF_MSB = hoff >> 8;
		map->slicer_extra_offsets.FOFF = foff;
		map->slicer_control_1.FISET = fiset;
		saa7113h_write (client, &map->slicer_vertical_offset);
		saa7113h_write (client, &map->slicer_extra_offsets);
		saa7113h_write (client, &map->slicer_horizontal_offset);
		saa7113h_write (client, &map->slicer_control_1);
		break;
	}

	case DECODER_SET_INPUT: {
		int *iarg = arg;

		SAA7113H_DEBUG_PRINTK ("DECODER_SET_INPUT: %d\n", *iarg);

		map->analog_input_control_1.MODE = *iarg;
		saa7113h_write (client, &map->analog_input_control_1);

		/* bypass chrominance trap for modes 4..7 */
		map->luminance_control.BYPS = (*iarg > 3) ? 1 : 0;
		saa7113h_write (client, &map->luminance_control);

		/* assume poor quality signals from tuner sources */
		map->sync_control.HTC = (*iarg <= 1) ? SAA7113H_HTC_TV : SAA7113H_HTC_FAST;
		saa7113h_write (client, &map->sync_control);
		break;
	}

	case DECODER_SET_OUTPUT: {
		int *iarg = arg;

		SAA7113H_DEBUG_PRINTK ("DECODER_SET_OUTPUT: %d\n", *iarg);

		/* not much choice of outputs */
		if (*iarg != 0) {
			return -EINVAL;
		}
		break;
	}

	case DECODER_ENABLE_OUTPUT: {
		int *iarg = arg;

		SAA7113H_DEBUG_PRINTK ("DECODER_ENABLE_OUTPUT: %d\n", *iarg);

		/*
		 * RJ: If output should be disabled (for playing videos), we also need
		 * a open PLL. The input is set to 0 (where no input source is
		 * connected), although this is not necessary.
		 * If output should be enabled, we have to reverse the above.
		 */
		if (*iarg) {
			/* Lock horizontal PLL */
			map->sync_control.HPLL = SAA7113H_HPLL_CLOSED;

			/* Enable real-time output */
			map->output_control_1.OERT = SAA7113H_OERT_ENABLE;

			/* Enable YUV output data */
			map->output_control_1.OEYC = SAA7113H_OEYC_ENABLE;

			/* Handle non-interlaced sources */
			map->sync_control.FOET = 1;
      
		} else {
			/* Unlock horizontal PLL */
			map->sync_control.HPLL = SAA7113H_HPLL_OPEN;

			/* Disable real-time output */
			map->output_control_1.OERT = SAA7113H_OERT_DISABLE;

			/* Disable YUV output data */
			map->output_control_1.OEYC = SAA7113H_OEYC_DISABLE;
		}

		/* Enable the correct output pins */
		map->output_control_2.RTSE0 = 0;
		map->output_control_2.RTSE1 = 2;

		/* Write modified to chip */
		saa7113h_write (client, &map->sync_control);
		saa7113h_write (client, &map->output_control_1);
		saa7113h_write (client, &map->output_control_2);

		break;
	}

	case DECODER_SET_PICTURE: {
		struct video_picture *pic = arg;

		SAA7113H_DEBUG_PRINTK ("DECODER_SET_PICTURE\n");

		if (saa7113h->bright != pic->brightness) {
			/* We want 0 to 255 we get 0-65535 */
			saa7113h->bright = pic->brightness;
			map->luminance_brightness.BRIG = saa7113h->bright >> 8;
			saa7113h_write (client, &map->luminance_brightness);
		}
		if (saa7113h->contrast != pic->contrast) {
			/* We want 0 to 127 we get 0-65535 */
			saa7113h->contrast = pic->contrast;
			map->luminance_contrast.CONT = saa7113h->contrast >> 9;
			saa7113h_write (client, &map->luminance_contrast);
		}
		if (saa7113h->sat != pic->colour) {
			/* We want 0 to 127 we get 0-65535 */
			saa7113h->sat = pic->colour;
			map->chrominance_saturation.SATN = saa7113h->sat >> 9;
			saa7113h_write (client, &map->chrominance_saturation);
		}
		if (saa7113h->hue != pic->hue) {
			/* We want -128 to 127 we get 0-65535 */
			saa7113h->hue = pic->hue;
			map->chrominance_hue_control.HUE = (saa7113h->hue - 32768) >> 8;
			saa7113h_write (client, &map->chrominance_hue_control);
		}
		break;
	}

	default:
		SAA7113H_DEBUG_PRINTK ("<invalid function 0x%08X>\n", cmd);
		return -EINVAL;
	}

	saa7113h_dump_regs (client);
	return 0;
}

/*---------------------------------------------------------------------- 
 *	Modularization
 *----------------------------------------------------------------------*/

MODULE_AUTHOR      ("Raphael Dorado <rdorado@pacbell.net> and Ferenc Bakonyi <fero@drama.obuda.kando.hu>");
MODULE_DESCRIPTION ("SAA7113H driver");
MODULE_LICENSE     ("GPL");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)
module_param       (debug, int, 0644);
#else
MODULE_PARM	   (debug, "i");
#endif
MODULE_PARM_DESC   (debug, "Debug level: 0 = silent, 1 = verbose, 2 = very verbose (default = 0).");

int __init init_saa7113h (void) 
{
  return i2c_add_driver (&i2c_driver_saa7113h);
}

void __exit cleanup_saa7113h (void) 
{
  i2c_del_driver (&i2c_driver_saa7113h);
}

module_init (init_saa7113h);
module_exit (cleanup_saa7113h);

